﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Globalization;
using System.Linq;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Serialization;

namespace ProDivingAdmin2salmon14
{
    /// <summary>
    /// The contest class.
    /// This class encapsulates everything needed for a contest.
    /// </summary>
    public class Contest : IContest
    {
        #region Public data members
        public string contestName { get; set; }
        public static ObservableCollection<string> judgeScores;
        public ObservableCollection<string> backupJudgeScores;
        public static List<Judge> judgeList { get; set; }
        public List<Judge> backupJudgeList { get; set; }
        public int amountOfJudges { get; set; }
        public int amountOfDives { get; set; }
        public DiverList diverList { get; set; }
        public DiverList rankingList { get; set; }
        public bool isDone { get; set; }
        public bool isFemale { get; set; }
        public bool isNextDive { get; set; }
        public int divingHeight { get; set; }
        public int minAge { get; set; }
        public static Dictionary<string, Tuple<float, string>> validDives { get; set; }
        public int diver { get; set; }
        public int dive { get; set; }
        public int diveCounter { get; set; }
        public string filePath { get; set; }
        public event DelegateShowJudgeForm EventShowJudgeForm = null;
        #endregion
        
        #region Interface methods

        public void SetContestName(string contestName) 
        {
            this.contestName = contestName;
        }

        public string GetContestName() 
        {
            return this.contestName;
        }

        public string GetFilePath()
        {
            return this.filePath;
        }

        public void SetFilePath(string filePath)
        {
            this.filePath = filePath;
        }

        public int GetDiveOrderCounter()
        {
            return this.diveCounter;
        }

        public void DiveOrderCounterIncrement()
        {
            this.diveCounter++;
        }

        public int GetAmountOfDives() 
        {
            return this.amountOfDives;
        }

        public int GetCurrentDive() 
        {
            return this.dive;
        }

        public void BackupJudgeScores()
        {
            foreach (var score in Contest.judgeScores)
            {
                backupJudgeScores.Add(score);
            }
        }

        /// <summary>
        /// This function is called to fill the rankinglist with divers from the diverList
        /// </summary>
        public void PopulateRankingList()
        {
            foreach (var diver in diverList)
            {
                rankingList.Add(diver);
            }
            List<Diver> sortedList = this.rankingList.OrderBy(x => x.totalScore).ToList();
            sortedList.Reverse();
            this.rankingList = new DiverList(sortedList);
        }

        public Contest GetContest()
        {
            return this;
        }

        public DiverList GetDiverList() 
        {
            this.rankingList.Clear();
            this.PopulateRankingList();
            return this.rankingList;
        }

        /// <summary>
        /// Called when user deletes a dive from a diver in the add/edit form.
        /// </summary>
        /// <param name="diver"></param>
        /// <param name="index"></param>
        public void DeleteDiveFromDiver(Diver diver, int index) 
        {
            foreach (var diverInList in this.diverList)
            {
                if (diver.ID == diverInList.ID)
                {
                    diverInList.diveList.RemoveAt(index);
                    return;
                }
            }
        }

        public void AddDiverToList(Diver diver) 
        {
            this.diverList.Add(diver);
        }

        public void AddDiveToDiver(Diver diver, Dive dive) 
        {
            foreach (var diverInList in this.diverList) 
            {
                if (diver.ID == diverInList.ID) 
                {
                    diverInList.diveList.Add(dive);
                    return;
                }
            }
        }

        public void EditDiver(int diverIndx, Diver newDiver)
        {
            diverList[diverIndx] = newDiver;
        }

        public void EditDiveToDiver(Diver diver, Dive newDive, int diveIndx)
        {
            foreach (var diverInList in this.diverList)
            {
                if (diver.ID == diverInList.ID)
                {
                    diverInList.diveList[diveIndx] = newDive;
                    return;
                }
            }
        }

        public List<Dive> GetDiveListFromDiver(Diver diver) 
        {
            foreach (var diverInList in this.diverList) 
            {
                if (diver.ID == diverInList.ID) 
                {
                    return diverInList.diveList;
                }
            }
            return new List<Dive>();
        }

        /// <summary>
        /// Function that returns the bool stating if the next dive button should be enabled.
        /// </summary>
        /// <returns></returns>
        public bool GetIsNextDive() 
        {
            return this.isNextDive;
        }

        public bool GetIsDone() 
        {
            return this.isDone;
        }

        public DiverList GetListOfDivers() 
        {
            return diverList;
        }

        /// <summary>
        /// This is the function that returns the order in which the divers perform their dives.
        /// </summary>
        /// <returns></returns>
        public List<DisplayDive> GetDiverOrder() 
        {
            List<DisplayDive> returnList = new List<DisplayDive>();
            int order = 1;

            try
            {
                for (int i = 0; i < diverList[0].diveList.Count; i++)
                {
                    foreach (var diver in diverList)
                    {
                        returnList.Add(new DisplayDive(order, diver.fullName, diver.nationality, diver.diveList[i].diveCode, this.divingHeight.ToString(), diver.diveList[i].GetDifficulty(), diver.diveList[i].GetName()));
                        order++;
                    }
                }
            }
            catch (ArgumentOutOfRangeException) 
            {
                return returnList;  
            }
            return returnList;
        }

        /// <summary>
        /// Returns a list of connected judges.
        /// </summary>
        /// <returns></returns>
        public List<Judge> GetConnectedJudgeList() 
        {
            try
            {
            return new List<Judge>(CommunicationServer._judgeSockets.Keys);
        }
            catch
        {
                return new List<Judge>();
            }
        }

        /// <summary>
        /// Sets all connected judges scores to 0.0f
        /// </summary>
        public void ResetConnectedJudgesScore()
        {
            foreach (var judge in CommunicationServer._judgeSockets.Keys)
            {
                judge.score = 0.0f;
            }
        }

        /// <summary>
        /// This function sends the next dive to the judges.
        /// Before sending the function checks to see if the list of dives is empty, thus all dives have been performed.
        /// It also checks if a diver is marked as injured or disqualified and acts accordingly.
        /// </summary>
        public void NextDive()
        {
            diver++;

            if (diver > diverList.Count - 1)
            {
                diver = 0;
                dive++;
            }

            Console.WriteLine(diver + "," + dive);

            if (this.dive == this.amountOfDives)
            {
                System.Console.WriteLine("ERROR IDIOT!\nContest done!");
                isDone = true;
            }
            else if (diverList[diver].isDisqualified)
            {
                //Do nothin
                //this.DQDiver(diverList[diver]);
                diverList[diver].diveList[dive].diveScore = 0.0f;
                this.diveCounter++;
                isNextDive = true;
            }
            else if (diverList[diver].isInjured)
            {
                //Do Nothing
                //diverList[diver].diveList[dive].diveScore = 0.0f;
                diverList[diver].diveList[dive].diveScore = 0.0f;
                this.diveCounter++;
                isNextDive = true;
            }
            else
            {
                Console.WriteLine(diverList[diver].fullName + diverList[diver].diveList[dive].diveCode);
                SendDive(diverList[diver].diveList[dive].diveCode);
                isNextDive = false;
                this.diveCounter++;
            }
            
        }

        /// <summary>
        /// A simple function to get the current diver. The idea is that the view is going to display the current diver.
        /// </summary>
        /// <returns> A diver. </returns>
        public Diver GetCurrentDiver() 
        {
            return diverList[diver];
        }
        
        /// <summary>
        /// A simple function to get the next diver. The idea is that the view is going to display the next diver.
        /// </summary>
        /// <returns>A diver</returns>
        public Diver GetNextDiver() 
        {
            if (diver + 1 <= diverList.Count - 1)
            {
                return diverList[diver + 1];
            }
            else 
            {
                return diverList[0];
            }
        }

        public int GetAmountOfJudges() 
        {
            return this.amountOfJudges;
        }

        /// <summary>
        /// A function that sets all divescores of a disqualified diver to 0.
        /// </summary>
        /// <param name="diver"></param>
        public void DQDiver(Diver diver) 
        {
            foreach (var dive in diver.diveList) 
            {
                dive.diveScore = 0.0f;
            }
        }

        /// <summary>
        /// A function to generate a list of all the valid dives for the given contest type.
        /// This function is called within the constructor.
        /// </summary>
        public void GenerateValidDives()
        {
            int lastChar = 0;
            if (this.divingHeight == 1)
            {
                
                foreach (var dive in FileManager.diveList)
                {
                    //System.Console.WriteLine("trav divelist");
                    lastChar = dive.Key.Count() - 1;
                    if (dive.Key[lastChar] == '1')
                    {
                        if (dive.Key[0] == '1' && dive.Key[2] >= '4')
                        {
                            Contest.validDives.Add(dive.Key, dive.Value);
                        }
                        else if (dive.Key[0] > '1' && dive.Key[0] < '5' && dive.Key[2] >= '3')
                        {
                            Contest.validDives.Add(dive.Key, dive.Value);
                        }
                        else if (dive.Key[0] == '5' && dive.Key[2] >= '2')
                        {
                            Contest.validDives.Add(dive.Key, dive.Value);
                        }
                        else
                        {
                            Contest.validDives.Add(dive.Key, dive.Value);
                        }
                    }
                }
            }
            else if (this.divingHeight == 3)
            {

                foreach (var dive in FileManager.diveList)
                {
                    lastChar = dive.Key.Count() - 1;
                    if (dive.Key[lastChar] == '3')
                    {
                        if (dive.Key[0] == '1' && dive.Key[2] >= '5')
                        {
                            Contest.validDives.Add(dive.Key, dive.Value);
                        }
                        else if (dive.Key[0] > '1' && dive.Key[0] < '6' && dive.Key[2] >= '3')
                        {
                            Contest.validDives.Add(dive.Key, dive.Value);
                        }
                        else
                        {
                            Contest.validDives.Add(dive.Key, dive.Value);
                        }
                    }
                }
            }
            else
            {
                foreach (var dive in FileManager.diveList)
                {
                    lastChar = dive.Key.Count() - 1;
                    if (dive.Key[lastChar] > '3' || dive.Key[lastChar] == '0')
                    {
                        Console.WriteLine("Adding dive: " + dive.Key);
                        Contest.validDives.Add(dive.Key, dive.Value);
                    }
                }
            }
        }
        #endregion

        #region Public constructs

        public Contest()
        {
            isDone = false;
            diver = -1;
            dive = 0;
            this.isFemale = false;
            this.minAge = 0;
            this.divingHeight = 0;
            this.amountOfJudges = 0;
            this.diveCounter = 0;
            this.filePath = "";

            this.diverList = new DiverList();

            judgeList = new List<Judge>();
            backupJudgeList = new List<Judge>();

            rankingList = new DiverList();

            judgeScores = new ObservableCollection<string>();
            judgeScores.CollectionChanged += new NotifyCollectionChangedEventHandler(JudgeScoreChanged);
            backupJudgeScores = new ObservableCollection<string>();

            FileManager.LoadDives("DivingScores_and_Names2salmon14.txt");

        }

        public Contest(bool empty)
        {
            isDone = false;
            diver = -1;
            dive = 0;
            this.isFemale = false;
            this.minAge = 0;
            this.divingHeight = 0;
            this.amountOfJudges = 0;
            this.filePath = "";

            this.diverList = new DiverList();

            judgeList = new List<Judge>();
            rankingList = new DiverList();

            validDives = new Dictionary<string, Tuple<float, string>>();
            backupJudgeScores = new ObservableCollection<string>();
            judgeScores.CollectionChanged += new NotifyCollectionChangedEventHandler(JudgeScoreChanged);
        }

        /// <summary>
        /// The constructor for the Contest class takes a bunch arguments to set up the contest according to given specifications.
        /// </summary>
        /// <param name="female">A bool value stating wheter the contest if for females or males</param>
        /// <param name="minAge">An int stating the age group for the contest</param>
        /// <param name="height">The height of the dives</param>
        /// <param name="judges">The amount of judges for the contest</param>
        public Contest(bool female, int minAge, int divingHeight, int judges, string diversPath, string judgesPath)
        {
            isDone = false;
            diver = -1;
            dive = 0;
            this.isFemale = female;
            this.minAge = minAge;
            this.divingHeight = divingHeight;
            this.amountOfJudges = judges;
            this.backupJudgeList = new List<Judge>();
            this.filePath = "";
            if (minAge == 18)
            {
                if (this.isFemale)
                {
                    this.amountOfDives = 5;
                }
                else
                {
                    this.amountOfDives = 6;
                }
            }

            backupJudgeScores = new ObservableCollection<string>();
            judgeScores = new ObservableCollection<string>();
            judgeScores.CollectionChanged += new NotifyCollectionChangedEventHandler(JudgeScoreChanged);
            validDives = new Dictionary<string, Tuple<float, string>>();
            rankingList = new DiverList();
            FileManager.LoadDives("DivingScores_and_Names2salmon14.txt");

            this.GenerateValidDives();

            this.diverList = new DiverList(diversPath);
            PopulateRankingList();

            Contest.judgeList = new List<Judge>(FileManager.LoadJudges(judgesPath));

            Console.WriteLine("Amount of divers in diverList: " + diverList.Count);

            if (judgeList.Count > this.amountOfJudges || judgeList.Count < this.amountOfJudges)
            {
                System.Console.WriteLine("Error wrong amount of judges for contest!");
                isDone = true;
            }
        }
        
        #endregion

        #region Public methods

        public void ReorderJudgeList()
        {
            for (int i = 0; i < judgeList.Count; i++)
            {
                judgeList[i].order = i;
            }
        }

        /// <summary>
        /// Removes a judge from the list based on the id paramater.
        /// </summary>
        /// <param name="id"></param>
        public void DeleteJudgeFromList(int id)
        {
            foreach (var judge in Contest.judgeList)
            {
                if (judge.ID == id)
                {
                    Contest.judgeList.Remove(judge);
                    break;
                }
            }
            this.BackupJudgeList();
        }

        /// <summary>
        /// This function edits some of the judge data at a given index in the júdgelist.
        /// </summary>
        /// <param name="judgeIndex"></param>
        /// <param name="newJudge"></param>
        public void EditJudge(int judgeIndex, Judge newJudge)
        {
            Contest.judgeList[judgeIndex].fullName = newJudge.fullName;
            Contest.judgeList[judgeIndex].ID = newJudge.ID;
            Contest.judgeList[judgeIndex].nationality = newJudge.nationality;
            this.BackupJudgeList();
        }

        /// <summary>
        /// A function to set the amount of judges in the contest.
        /// </summary>
        /// <param name="amountOfJudges"></param>
        public void SetAmountOfJudges(int amountOfJudges)
        {
            this.amountOfJudges = amountOfJudges;
        }

        public void BackupJudgeList()
        {
            this.backupJudgeList.Clear();
            foreach (var judge in judgeList)
            {
                this.backupJudgeList.Add(judge);
            }
        }
        /// <summary>
        /// This function is called when the judgeScore-list has been changed in any way.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="args"></param>
        private void JudgeScoreChanged(object sender, NotifyCollectionChangedEventArgs args) 
        {
            foreach (var judgeScore in judgeScores)
            {
                string[] elements = judgeScore.Split(',');
                System.Console.WriteLine("Adding score: " + elements[1]);
                if (!this.SetJudgeScore(Int32.Parse(elements[0]), float.Parse(elements[1], CultureInfo.InvariantCulture.NumberFormat)))
                {
                    System.Console.WriteLine("Invalid judge!");
                    return;
                }
            }
            return;
        }

        /// <summary>
        /// This is a function that sets the score from a judge into the judgescore.
        /// </summary>
        /// <param name="ID"></param>
        /// <param name="score"></param>
        /// <returns></returns>
        public bool SetJudgeScore(int ID, float score) 
        {
            foreach (var judge in CommunicationServer._judgeSockets.Keys) 
            {
                if (judge.ID == ID) 
                {
                    judge.score = score;
                    diverList[diver].diveList[dive].judgeScores.Add(new JudgeScore(judge.nationality, judge.score));

                    if (diverList[diver].diveList[dive].judgeScores.Count == this.amountOfJudges) 
                    {
                        this.diverList[diver].diveList[dive].CalculateScore();
                        this.diverList[diver].CalculateScore();

                        List<Diver> sortedList = this.rankingList.OrderBy(x => x.totalScore).ToList();
                        sortedList.Reverse();
                        this.rankingList = new DiverList(sortedList);
                        
                        isNextDive = true;
                    }
                    return true;
                }
            }
            return false;
        }


        /// <summary>
        /// This functions sends the current dive to the judges.
        /// </summary>
        /// <param name="diveCode"></param>
        public void SendDive(string diveCode)
        {
            for (int i = 0; i < diveCode.Length; i++) 
            {
                if (char.IsLetter(diveCode[i])) 
                {
                    diveCode = diveCode.Remove(i + 1);
                    break;
                }
            }

            CommunicationServer.sendText = "Dive:" + diveCode + ",Nr:" + diveCounter + "/" + diverList.Count * this.amountOfDives;
            CommunicationServer.SendDive();
        }

        public static void SetJudgeDC(Judge judge) 
        {
            foreach (var judgeL in judgeList) 
            {
                if (judgeL.ID == judge.ID) 
                {
                    judgeL.isConnected = false;
                    break;
                }
            }
        }

        /// <summary>
        /// This function gets a judge by providing the ID.
        /// </summary>
        /// <param name="ID"></param>
        /// <returns></returns>
        public static Judge GetJudgeByID(int ID) 
        {
            foreach (var judge in judgeList) 
            {
                if (judge.ID == ID && !judge.isConnected)
                {
                    judge.isConnected = true;
                    return judge;
                }
            }
            return new Judge("NONE", "NONE", 0, 0);
        }

        /// <summary>
        /// This function is called when the user starts a new contest. This is infact used as a constructor of sorts.
        /// All the parameters comes from info entered into the startupForm.
        /// </summary>
        /// <param name="female"></param>
        /// <param name="minAge"></param>
        /// <param name="divingHeight"></param>
        /// <param name="judges"></param>
        /// <param name="diversPath"></param>
        /// <param name="judgesPath"></param>
        /// <param name="filePath"></param>
        public void ResetData(bool female, int minAge, int divingHeight, int judges, string diversPath, string judgesPath, string filePath)
        {
            isDone = false;
            diver = -1;
            dive = 0;
            this.isFemale = female;
            this.minAge = minAge;
            this.divingHeight = divingHeight;
            this.amountOfJudges = judges;
            this.diveCounter = 0;
            this.filePath = filePath;
            if (minAge == 18)
            {
                if (this.isFemale)
                {
                    this.amountOfDives = 5;
                }
                else
                {
                    this.amountOfDives = 6;
                }
            }

            judgeScores = new ObservableCollection<string>();
            judgeScores.CollectionChanged += new NotifyCollectionChangedEventHandler(JudgeScoreChanged);
            validDives = new Dictionary<string, Tuple<float, string>>();
            rankingList = new DiverList();

            this.GenerateValidDives();

            this.diverList = new DiverList(diversPath);
            PopulateRankingList();
            Contest.judgeList = new List<Judge>(FileManager.LoadJudges(judgesPath));

            if (judgeList.Count > this.amountOfJudges || judgeList.Count < this.amountOfJudges)
            {
                System.Console.WriteLine("Error wrong amount of judges for contest!");
                if (EventShowJudgeForm != null) 
                {
                    EventShowJudgeForm();
                }
            }

            BackupJudgeList();
        }

        public void GetJudgeIDs(string[] ids)
        {
            int counter = 0;
            foreach (var judge in judgeList)
            {
                ids[counter] = judge.ID.ToString();
                counter++;
            }
        }

        /// <summary>
        /// This function is called when the user wants to modify the judgelist in a contest.
        /// </summary>
        /// <returns></returns>
        public List<Judge> GetJudgeList()
        {
            this.BackupJudgeList();
            return this.backupJudgeList;
        }
        #endregion



    }//Contest
}//Namespace